<?php


namespace wechat;

use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use WeChatPay\Util\PemUtil;
use WeChatPay\Crypto\AesGcm;

class AppletPay
{

    /**
     * 使用
     *     $body = file_get_contents('php://input');
     * $header = $this->request->header();
     * write_content_log($body,'callback');
     * write_content_log($header,'callback');
     * $re = (new \wechat\AppletPay())->callback($body,$header);
     *
     * if ($re == false){
     * write_content_log('解析失败','order_error');
     * return false;
     * }
     * OrderLogic::callback($re);
     * write_content_log('完成','order_error');
     * @var string
     */


// 设置参数

// 商户号
    private $merchantId = '1635258840';
    private $appid = 'wxfe82647caa119159';
    private $apiv3Key = 'o7E9faXN1zxy3qj7k3j6o5xvY81tGSE7';// 在商户平台上设置的APIv3密钥

//// 从本地文件中加载「商户API私钥」，「商户API私钥」会用来生成请求的签名
    private $merchantPrivateKeyFilePath = 'file://' . ROOT_PATH . 'cert/apiclient_key.pem';
    private $merchantPrivateKeyInstance = '';
//// 「商户API证书」的「证书序列号」
    private $merchantCertificateSerial = '188F83AB3A4818E90BC856B7CDA4F3DC04B555FA';

// 从本地文件中加载「微信支付平台证书」，用来验证微信支付应答的签名
    // private $platformCertificateFilePath = 'file://' . ROOT_PATH . 'cert/cert.pem';
    private $platformCertificateFilePath = 'file://' . ROOT_PATH . 'cert/wechatpay_40296226CB8C7F94C69EFDAAE4D2B91B09626882.pem';
    private $platformPublicKeyInstance = '';
// 从「微信支付平台证书」中获取「证书序列号」
    private $platformCertificateSerial = '40296226CB8C7F94C69EFDAAE4D2B91B09626882';
    private $callback = '/api/client/home/callback';
    private $refundCallback = '/api/client/home/refundCallback';


    public function __construct()
    {

        $this->merchantPrivateKeyInstance = Rsa::from($this->merchantPrivateKeyFilePath, Rsa::KEY_TYPE_PRIVATE);
        $this->platformPublicKeyInstance = Rsa::from($this->platformCertificateFilePath, Rsa::KEY_TYPE_PUBLIC);
        $this->platformCertificateSerial = PemUtil::parseCertificateSerialNo($this->platformCertificateFilePath);
    }


    // 初始化 客户端对象
    public function getInstance()
    {
        return Builder::factory([
            'mchid' => $this->merchantId,
            'serial' => $this->merchantCertificateSerial,
            'privateKey' => $this->merchantPrivateKeyInstance,
            'certs' => [
                $this->platformCertificateSerial => $this->platformPublicKeyInstance,
            ],
        ]);

    }


    // 统一下单

    /**
     * 小程序
     * {
     * "mchid": "1900006XXX",
     * "out_trade_no": "1217752501201407033233368318",
     * "appid": "wxdace645e0bc2cXXX",
     * "description": "Image形象店-深圳腾大-QQ公仔",
     * "notify_url": "https://www.weixin.qq.com/wxpay/pay.php",
     * "amount": {
     * "total": 1,
     * "currency": "CNY"
     * },
     * "payer": {
     * "openid": "o4GgauInH_RCEdvrrNGrntXDuXXX"
     * }
     * }
     * author sl
     * 2022-11-15
     */
    public function unifiedOrder($order_number, $money, $openid)
    {
        try {
            $res = $this->getInstance()
                ->chain('v3/pay/transactions/jsapi')
                ->post([
                    'json' => [
                        'mchid' => $this->merchantId,
                        'appid' => $this->appid,
                        'out_trade_no' => $order_number,
                        'description' => 'description',
                        'notify_url' => request()->domain() . $this->callback,
                        'amount' => [
                            'total' => $money * 100,
                            'currency' => 'CNY',
                        ], 'payer' => [
                            'openid' => $openid,
                        ],
                    ]
                ]);
            if ($res->getStatusCode() !== 200) throw new \Exception('请求失败');
            $stringBody = $res->getBody();

            $stringBody = (string)$stringBody;
            $stringBody = json_decode($stringBody, true);
            return $this->getSign($stringBody['prepay_id']);
        } catch (\Exception $e) {
            return false;
        }
    }


    // 签名
    private function getSign($prepay_id)
    {


        $params = [
            'appId' => $this->appid,
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr' => Formatter::nonce(),
            'package' => 'prepay_id=' . $prepay_id,
        ];
        $params += ['paySign' => Rsa::sign(
            Formatter::joinedByLineFeed(...array_values($params)),
            $this->merchantPrivateKeyInstance
        ), 'signType' => 'RSA'];

        return $params;
    }


    // 获取证书列表
    public function certificates()
    {
        $res = $this->getInstance()
            ->chain('v3/certificates')->get(
            //  ['debug' => true] // 调试模式，https://docs.guzzlephp.org/en/stable/request-options.html#debug
            );
        if ($res->getStatusCode() !== 200) throw new \Exception('请求失败');
        $stringBody = (string)$res->getBody();
        halt(json_decode($stringBody, true));
    }

    /**
     * 支付回调
     * @param $inBody
     * @param $header
     * author sl
     * 2022-11-28
     */
    public function callback($inBody, $header)
    {

        $inWechatpaySignature = $header['wechatpay-signature'];// 请根据实际情况获取
        $inWechatpayTimestamp = $header['wechatpay-timestamp'];// 请根据实际情况获取
        $inWechatpaySerial = $header['wechatpay-serial'];// 请根据实际情况获取
        $inWechatpayNonce = $header['wechatpay-nonce'];// 请根据实际情况获取

        if ($inWechatpaySerial != $this->platformCertificateSerial) {
            write_content_log("serial 不符合\n " . $inBody, 'callback_error');
            return false;
        }
        // 检查通知时间偏移量，允许5分钟之内的偏
        $timeOffsetStatus = 30000 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串

            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $this->platformPublicKeyInstance
        );

        if ($verifiedStatus === false) {
            write_content_log("验签失败 不符合\n " . $inBody, 'callback_error');
            return false;
        }
        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($inBody, true);

        // 使用PHP7的数据解构语法，从Array中解构并赋值变量
        ['resource' => [
            'ciphertext' => $ciphertext,
            'nonce' => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;
        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, $this->apiv3Key, $nonce, $aad);
        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);
        write_content_log("验签成功 " . $inBody, 'callback_success');
        return $inBodyResourceArray;
    }


    /**
     * 退款回调
     * @param $inBody
     * @param $header
     * author sl
     * @return array|false
     * 2022-11-29
     */
    public function refundCallback($inBody, $header)
    {

        $inWechatpaySignature = $header['wechatpay-signature'];// 请根据实际情况获取
        $inWechatpayTimestamp = $header['wechatpay-timestamp'];// 请根据实际情况获取
        $inWechatpaySerial = $header['wechatpay-serial'];// 请根据实际情况获取
        $inWechatpayNonce = $header['wechatpay-nonce'];// 请根据实际情况获取

        if ($inWechatpaySerial != $this->platformCertificateSerial) {
            write_content_log("serial 不符合\n " . $inBody, 'refundCallbackError');
            return false;
        }
        // 检查通知时间偏移量，允许5分钟之内的偏
        $timeOffsetStatus = 30000 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
        $verifiedStatus = Rsa::verify(
        // 构造验签名串

            Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
            $inWechatpaySignature,
            $this->platformPublicKeyInstance
        );

        if ($verifiedStatus === false) {
            write_content_log("验签失败 不符合\n " . $inBody, 'refundCallbackError');
            return false;
        }
        // 转换通知的JSON文本消息为PHP Array数组
        $inBodyArray = (array)json_decode($inBody, true);

        // 使用PHP7的数据解构语法，从Array中解构并赋值变量
        ['resource' => [
            'ciphertext' => $ciphertext,
            'nonce' => $nonce,
            'associated_data' => $aad
        ]] = $inBodyArray;
        // 加密文本消息解密
        $inBodyResource = AesGcm::decrypt($ciphertext, $this->apiv3Key, $nonce, $aad);
        // 把解密后的文本转换为PHP Array数组
        $inBodyResourceArray = (array)json_decode($inBodyResource, true);
        write_content_log("验签成功 " . $inBody, 'refundCallbackSuccess');
        return $inBodyResourceArray;
    }

    // 异步退款
    public function refundPromise($out_trade_no, $out_refund_no, $order_money, $refund_money)

    {

        $promise = $this->getInstance()
            ->chain('v3/refund/domestic/refunds')
            ->postAsync([

                'json' => [

                    //  'transaction_id' => '1217752501201407033233368018',

                    'out_trade_no' => $out_trade_no,

                    'out_refund_no' => $out_refund_no,
                    'amount' => [
                        'refund' => $refund_money * 100,
                        'total' => $order_money * 100,
                        'currency' => 'CNY',
                    ],
                    'notify_url' => request()->domain() . $this->refundCallback
                ],

            ])
            ->then(static function ($response) {
                $body = (string)$response->getBody();
                return json_decode($body, true);
            })
            ->otherwise(static function ($e) {

                // 异常错误处理

                //   echo $e->getMessage(), PHP_EOL;

                if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {

//                    $r = $e->getResponse();
//
//                    echo $r->getStatusCode() . ' ' . $r->getReasonPhrase(), PHP_EOL;
//
//                    $msg = (string)$r->getBody();
//
//                    halt(json_decode($msg,true));
                    return new \Exception($e->getMessage());


                }

                //    echo $e->getTraceAsString(), PHP_EOL;

            });

// 同步等待

        $promise->wait();


    }

    // 同步退款
    public function refund($out_trade_no, $out_refund_no, $order_money, $refund_money)

    {
        try {
            $res = $this->getInstance()
                ->chain('v3/refund/domestic/refunds')
                ->post([
                    'json' => [

                        //  'transaction_id' => '1217752501201407033233368018',

                        'out_trade_no' => $out_trade_no,

                        'out_refund_no' => $out_refund_no,
                        'amount' => [
                            'refund' => $refund_money * 100,
                            'total' => $order_money * 100,
                            'currency' => 'CNY',
                        ],
                        'notify_url' => request()->domain() . $this->refundCallback
                    ],
                ]);
            if ($res->getStatusCode() !== 200) throw new \Exception('请求失败');
            $stringBody = $res->getBody();

            $stringBody = (string)$stringBody;
            $stringBody = json_decode($stringBody, true);
            return $stringBody;
        } catch (\Exception $e) {
            if ($e instanceof \GuzzleHttp\Exception\RequestException && $e->hasResponse()) {
                $r = $e->getResponse();
                $msg = (string)$r->getBody();
                $msg = json_decode($msg, true);
                $msg['code'] = 999;
                return$msg;
             //   write_content_log($msg,'refundError');
                return new \Exception($msg['message']);
                // return new Exception($e->getMessage());

            }
            return ['message'=>'操作失败','code'=>999];
        }



    }


}